1 /*
2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021
3 License:   [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License].
4 Authors: Marcelo S. N. Mancini
5 
6 	Copyright Marcelo S. N. Mancini 2018 - 2021.
7 Distributed under the CC BY-4.0 License.
8    (See accompanying file LICENSE.txt or copy at
9 	https://creativecommons.org/licenses/by/4.0/
10 */
11 module hip.hiprenderer.shader.shadervar;
12 import hip.hiprenderer.shader.shader;
13 import hip.hiprenderer.renderer;
14 import hip.error.handler;
15 import hip.util.conv:to;
16 import hip.math.matrix;
17 import hip.api.graphics.color;
18 
19 /**
20 *   Changes how the Shader behaves based on the backend
21 */
22 enum ShaderHint : uint
23 {
24     NONE = 0,
25     GL_USE_BLOCK = 1<<0,
26     GL_USE_STD_140 = 1<<1,
27     D3D_USE_HLSL_4 = 1<<2,
28     /** 
29      * Meant for usage in uniform variables.
30      * That means one Shader Variable may not be sent to the backend depending on its requirements.
31      * An example for that is Array of Textures. In D3D11, it depends only on the resource being bound,
32      * while on Metal and GL3, they are required to be inside a MTLBuffer or being sent as an Uniform.
33      */ 
34     Blackbox = 1 << 3,
35     MaxTextures = 1 << 4
36 }
37 
38 /**
39 *   Should not be used directly. The D type inference can already set that for you.
40 *   This is stored by the variable to know how to access itself and comunicate the shader.
41 */
42 enum UniformType
43 {
44     boolean,
45     integer,
46     integer_array,
47     uinteger,
48     uinteger_array,
49     floating,
50     floating2,
51     floating3,
52     floating4,
53     floating2x2,
54     floating3x3,
55     floating4x4,
56     floating_array,
57     ///Special type that is implemented by renderers backend
58     texture_array,
59     none
60 }
61 
62 UniformType uniformTypeFrom(T)()
63 {
64     with(UniformType)
65     {
66         static if(is(T == bool)) return boolean;
67         else static if(is(T == int)) return integer;
68         else static if(is(T == int[])) return integer_array;
69         else static if(is(T == uint) || is(T == HipColor)) return uinteger;
70         else static if(is(T == uint[])) return uinteger_array;
71         else static if(is(T == float)) return floating;
72         else static if(is(T == float[2])) return floating2;
73         else static if(is(T == float[3])) return floating3;
74         else static if(is(T == float[4]) || is(T == HipColorf)) return floating4;
75         else static if(is(T == Matrix3)) return floating3x3;
76         else static if(is(T == Matrix4)) return floating4x4;
77         else static if(is(T == float[])) return floating_array;
78         else static if(is(T == IHipTexture[])) return texture_array;
79         else return none;
80     }
81 }
82 
83 /**
84 *   Struct that holds uniform/cbuffer information for Direct3D and OpenGL shaders. It can be any type.
85 *   Its data is accessed by the ShaderVariableLayout when sendVars is called. Thus, depending on its
86 *   corrensponding type, its data is uploaded to the GPU.
87 */
88 struct ShaderVar
89 {
90     import hip.util.data_structures:Array;
91     import std.traits;
92     void[] data;
93     string name;
94     ShaderTypes shaderType;
95     UniformType type;
96     size_t singleSize;
97     bool isDynamicArrayReference;
98 
99     bool isDirty = true;
100     private bool _isBlackboxed = false;
101     public bool isBlackboxed() const { return _isBlackboxed;}
102 
103 
104     size_t varSize() const{return data.length;}
105     size_t length() const {return varSize / singleSize;}
106 
107     const T get(T)()
108     {
109         static if(isDynamicArray!T)
110             return cast(T)data;
111         else
112             return *(cast(T*)this.data.ptr);
113     }
114 
115     bool setBlackboxed(T)(T value)
116     {
117         import core.stdc.string;
118         if(value.sizeof != varSize || !_isBlackboxed) return false;
119         isDirty = true;
120         memcpy(data.ptr, &value, varSize);
121         return true;
122     }
123     bool set(T)(T value, bool validateData)
124     {
125         import core.stdc.string;
126         static assert(uniformTypeFrom!T != UniformType.none, "Invalid type "~T.stringof);
127         static if(isDynamicArray!T)
128         {
129             memcpy(data.ptr, value.ptr, value.length * T.init[0].sizeof);
130         }
131         else
132         {
133             if(value.sizeof != varSize) 
134                 return false;
135             static if(is(T == Matrix3) || is(T == Matrix4))
136                 value = HipRenderer.getMatrix(value);
137 
138             if(!_isBlackboxed && validateData && value == get!T)
139                 return true;
140             memcpy(data.ptr, &value, varSize);
141         }
142         isDirty = true;
143         return true;
144     }
145     auto opAssign(T)(T value)
146     {
147         static if(is(T == ShaderVar))
148         {
149             this.data = value.data;
150             this.name = value.name;
151             this.shaderType = value.shaderType;
152             this.singleSize = value.singleSize;
153         }
154         else
155             ErrorHandler.assertLazyExit(this.set(value), "Value set for '"~name~"' is invalid.");
156         return this;
157     }
158 
159     private void throwOnOutOfBounds(size_t index)
160     {
161         switch(type) with(UniformType)
162         {
163             case integer_array:
164                 ErrorHandler.assertExit(index < length/singleSize, "Index out of bounds on shader variable "~name);
165                 break;
166             case uinteger_array:
167                 ErrorHandler.assertExit(index < length/singleSize, "Index out of bounds on shader variable "~name);
168                 break;
169             case floating_array:
170                 ErrorHandler.assertExit(index < length/singleSize, "Index out of bounds on shader variable "~name);
171                 break;
172             case floating2:
173                 ErrorHandler.assertExit(index < 2, "Index out of bounds on shader variable "~name);
174                 break;
175             case floating3:
176                 ErrorHandler.assertExit(index < 3, "Index out of bounds on shader variable "~name);
177                 break;
178             case floating4:
179                 ErrorHandler.assertExit(index < 4, "Index out of bounds on shader variable "~name);
180                 break;
181             case floating2x2:
182                 ErrorHandler.assertExit(index < 4, "Index out of bounds on shader variable "~name);
183                 break;
184             case floating3x3:
185                 ErrorHandler.assertExit(index < 9, "Index out of bounds on shader variable "~name);
186                 break;
187             case floating4x4:
188                 ErrorHandler.assertExit(index < 16, "Index out of bounds on shader variable "~name);
189                 break;
190             default:
191                 ErrorHandler.assertExit(false, "opIndex is unsupported in var of type "~to!string(type));
192         }
193     }
194 
195     auto opIndexAssign(T)(T value, size_t index)
196     {
197         import core.stdc.string;
198         throwOnOutOfBounds(index);
199         ErrorHandler.assertExit(index*singleSize + T.sizeof <= varSize, "Value assign of type "~T.stringof~" at index "~to!string(index)~
200         " is invalid for shader variable "~name~" of type "~to!string(type));
201         memcpy(cast(ubyte*)data + singleSize*index, &value, T.sizeof);
202         return value;
203     }
204 
205     ref auto opIndex(size_t index)
206     {
207         throwOnOutOfBounds(index);
208         switch(type) with(UniformType)
209         {
210             case integer_array: return get!(int[])[index];
211             case uinteger_array: return get!(uint[])[index];
212             case floating_array: return get!(float[])[index];
213             case floating2: return get!(float[2])[index];
214             case floating3: return get!(float[3])[index];
215             case floating4: return get!(float[4])[index];
216             case floating2x2: return get!(float[4])[index];
217             case floating3x3: return get!(float[9])[index];
218             case floating4x4: return get!(float[16])[index];
219             default:
220                 ErrorHandler.assertExit(false, "opIndex is unsupported in var of type "~to!string(type));
221                 return 0;
222         }
223     }
224 
225     static ShaderVar* create(ShaderTypes t, string varName, bool data){return ShaderVar.create(t, varName, &data, UniformType.boolean, data.sizeof, data.sizeof);}
226     static ShaderVar* create(ShaderTypes t, string varName, int data){return ShaderVar.create(t, varName, &data, UniformType.integer, data.sizeof, data.sizeof);}
227     static ShaderVar* create(ShaderTypes t, string varName, uint data){return ShaderVar.create(t, varName, &data, UniformType.uinteger, data.sizeof, data.sizeof);}
228     static ShaderVar* create(ShaderTypes t, string varName, float data){return ShaderVar.create(t, varName, &data, UniformType.floating, data.sizeof, data.sizeof);}
229     static ShaderVar* create(ShaderTypes t, string varName, float[2] data){return ShaderVar.create(t, varName, &data, UniformType.floating2, data.sizeof, data[0].sizeof);}
230     static ShaderVar* create(ShaderTypes t, string varName, float[3] data){return ShaderVar.create(t, varName, &data, UniformType.floating3, data.sizeof, data[0].sizeof);}
231     static ShaderVar* create(ShaderTypes t, string varName, float[4] data){return ShaderVar.create(t, varName, &data, UniformType.floating4, data.sizeof, data[0].sizeof);}
232     static ShaderVar* create(ShaderTypes t, string varName, float[9] data){return ShaderVar.create(t, varName, &data, UniformType.floating3x3, data.sizeof, data[0].sizeof);}
233     static ShaderVar* create(ShaderTypes t, string varName, float[16] data){return ShaderVar.create(t, varName, &data, UniformType.floating4x4, data.sizeof, data[0].sizeof);}
234     static ShaderVar* create(ShaderTypes t, string varName, int[] data)
235     {
236         return ShaderVar.create(t, varName, data.ptr, UniformType.floating_array, int.sizeof*data.length, int.sizeof, true);
237     }
238     static ShaderVar* create(ShaderTypes t, string varName, uint[] data)
239     {
240         return ShaderVar.create(t, varName, data.ptr, UniformType.floating_array, uint.sizeof*data.length, uint.sizeof, true);
241     }
242     static ShaderVar* create(ShaderTypes t, string varName, float[] data)
243     {
244         return ShaderVar.create(t, varName, data.ptr, UniformType.floating_array, float.sizeof*data.length, float.sizeof, true);
245     }
246 
247     protected static ShaderVar* create(
248         ShaderTypes t,
249         string varName,
250         void* varData,
251         UniformType type,
252         size_t varSize,
253         size_t singleSize,
254         bool isDynamicArrayReference=false
255     )
256     {
257         import core.stdc.string : memcpy;
258         ErrorHandler.assertExit(isShaderVarNameValid(varName), "Variable '"~varName~"' is invalid.");
259         ShaderVar* s = new ShaderVar();
260         s.data = new void[varSize];
261         memcpy(s.data.ptr, varData, varSize);   
262         s.name = varName;
263         s.shaderType = t;
264         s.type = type;
265         s.isDynamicArrayReference = isDynamicArrayReference;
266         s.singleSize = singleSize;
267         return s;
268     }
269     public static ShaderVar* createBlackboxed(
270         ShaderTypes t,
271         string varName,
272         UniformType type,
273         size_t varSize,
274         size_t singleSize)
275     {
276         ErrorHandler.assertExit(isShaderVarNameValid(varName), "Variable '"~varName~"' is invalid.");
277         ShaderVar* s = new ShaderVar();
278         s.data = new void[varSize];
279         s.name = varName;
280         s.singleSize = singleSize;
281         s.shaderType = t;
282         s.type = type;
283         return s;
284     }
285 
286     void dispose()
287     {
288         type = UniformType.none;
289         shaderType = ShaderTypes.NONE;
290         singleSize = 0;
291         if(isDynamicArrayReference)
292         {
293             (cast(Array!(int)*)data).dispose();
294         }
295         else if(data != null)
296         {
297             import core.memory;
298             GC.free(data.ptr);
299             data = null;
300         }
301     }
302 }
303 
304 struct ShaderVarLayout
305 {
306     ShaderVar* sVar;
307     size_t alignment;
308     size_t size;
309 }
310 
311 /**
312 *   This class is meant to be created together with the Shaders.
313 *   
314 *   Those are meant to wrap the cbuffer from Direct3D and Uniform Block from OpenGL.
315 *
316 *   By wrapping the uniforms/cbuffers layouts, it is much easier to send those variables from any API.
317 */
318 class ShaderVariablesLayout
319 {
320     import hip.hiprenderer.shader.var_packing;
321 
322     ShaderVarLayout[string] variables;
323     private string[] namesOrder;
324     private string[] unusedBlackboxed;
325     string name;
326     ShaderTypes shaderType;
327     protected Shader owner;
328 
329     //Single block representation of variables content
330     protected void* data;
331     protected void* additionalData;
332     protected bool isAdditionalAllocated;
333     ///Can't unlock Layout
334     private bool isLocked;
335 
336     ///The hint are used for the Shader backend as a notifier
337     public immutable int hint;
338     protected size_t lastPosition;
339 
340     ///A function that must return a variable size when position = 0
341     private VarPosition function(
342         ref ShaderVar* v,
343         size_t lastAlignment,
344         bool isLast
345     ) packFunc;
346 
347 
348     /**
349     *   Use the layout name for mentioning the uniform/cbuffer block name.
350     *
351     *   Its members are the ShaderVar* passed
352     *
353     *   Params:
354     *       layoutName = From which block it will be accessed on the shader
355     *       t = What is the shader type that holds those variables
356     *       hint = Use ShaderHint for additional information, multiple hints may be passed
357     *       variables = Usually you won't pass any and use .append for writing less
358     */
359     this(string layoutName, ShaderTypes t, uint hint, ShaderVar*[] variables ...)
360     {
361         import core.stdc.stdlib:malloc;
362         this.name = layoutName;
363         this.shaderType = t;
364         this.hint = hint;
365 
366         switch(HipRenderer.getRendererType())
367         {
368             case HipRendererType.GL3:
369                 // if(hint & ShaderHint.GL_USE_STD_140)
370                     packFunc = &glSTD140;
371                 break;
372             case HipRendererType.D3D11:
373                 // if(hint & ShaderHint.D3D_USE_HLSL_4)
374                     packFunc = &dxHLSL4;  
375                 break;
376             case HipRendererType.METAL:
377                 packFunc = &glSTD140;
378                 break;
379             case HipRendererType.NONE:
380             default:break;
381         }
382         if(packFunc is null) packFunc = &nonePack;
383 
384         foreach(ShaderVar* v; variables)
385         {
386             ErrorHandler.assertExit(v.shaderType == t, "ShaderVariableLayout must contain only one shader type");
387             ErrorHandler.assertExit((v.name in this.variables) is null, "Variable named "~v.name~" is already in the layout "~name);
388             this.variables[v.name] = ShaderVarLayout(v, 0, 0);
389             namesOrder~= v.name;
390         }
391         calcAlignment();
392         data = malloc(getLayoutSize());
393         ErrorHandler.assertExit(data != null, "Out of memory");
394     }
395 
396     static ShaderVariablesLayout from(T)()
397     {
398         enum attr = __traits(getAttributes, T);
399         static if(is(typeof(attr[0]) == HipShaderVertexUniform))
400             enum shaderType = ShaderTypes.VERTEX;
401         else static if(is(typeof(attr[0]) == HipShaderFragmentUniform))
402             enum shaderType = ShaderTypes.FRAGMENT;
403         else static assert(false, 
404             "Type "~T.stringof~" doesn't have a HipShaderVertexUniform nor " ~ 
405             "HipShaderFragmentUniform attached to it."
406         );
407         static assert(
408             attr[0].name !is null,
409             "HipShaderUniform "~T.stringof~" must contain a name as it is required to work in Direct3D 11"
410         );
411         ShaderVariablesLayout ret = new ShaderVariablesLayout(attr[0].name, shaderType, 0);
412         static foreach(mem; __traits(allMembers, T))
413         {{
414             alias member = __traits(getMember, T.init, mem);
415             alias a = __traits(getAttributes, member);
416             static if(is(typeof(a[0]) == ShaderHint) && a[0] & ShaderHint.Blackbox)
417             {
418                 size_t length = 1;
419                 if(a[0] & ShaderHint.MaxTextures) length =  HipRenderer.getMaxSupportedShaderTextures();
420                 ret.appendBlackboxed(mem, uniformTypeFrom!(typeof(member)), length);
421             }
422             else
423             {
424                 ret.append(mem, __traits(getMember, T.init, mem));
425             }
426 
427         }}
428 
429         return ret;
430     }
431 
432     Shader getShader(){return owner;}
433     void lock(Shader owner)
434     {
435         calcAlignment();
436         this.owner = owner;
437         this.isLocked = true;
438     }
439 
440     /**
441     *   Calculates the shader variables alignment based on the packFunc passed at startup.
442     *   Those functions are based on the shader vendor and version. Align should be called
443     *   always when there is a change on the layout.
444     */
445     final void calcAlignment()
446     {
447         size_t lastAlign = 0;
448         for(int i = 0; i < namesOrder.length; i++)
449         {
450             ShaderVarLayout* l = &variables[namesOrder[i]];
451             VarPosition pos = packFunc(l.sVar, lastAlign, i == cast(int)namesOrder.length-1);
452             l.size = pos.size;
453             l.alignment = pos.startPos;
454             lastAlign = pos.endPos;
455         }
456         lastPosition = lastAlign;
457     }
458 
459 
460     void* getBlockData()
461     {
462         import core.stdc.string:memcpy;
463         foreach(v; variables)
464             memcpy(data+v.alignment, v.sVar.data.ptr, v.size);
465         return data;
466     }
467 
468     protected ShaderVariablesLayout append(string varName, ShaderVar* v)
469     {
470         import core.stdc.stdlib:realloc;
471         ErrorHandler.assertExit((varName in variables) is null, "Variable named "~varName~" is already in the layout "~name);
472         ErrorHandler.assertExit(!isLocked, "Can't append ShaderVariable after it has been locked");
473         variables[varName] = ShaderVarLayout(v, 0, 0);
474         namesOrder~= varName;
475         calcAlignment();
476         this.data = realloc(this.data, getLayoutSize());
477         ErrorHandler.assertExit(this.data != null, "Out of memory");
478         return this;
479     }
480 
481     /**
482     *   Appends a new variable to this layout.
483     *   Type is inferred.
484     */
485     ShaderVariablesLayout append(T)(string varName, T data)
486     {
487         return append(varName, ShaderVar.create(this.shaderType, varName, data));
488     }
489     /**
490     *   Appends a new variable to this layout.
491     *   Type is inferred.
492     */
493     ShaderVariablesLayout appendBlackboxed(string varName, UniformType t, size_t length)
494     {
495         ShaderVar* sV = HipRenderer.createShaderVar(this.shaderType, t, varName, length);
496         if(sV is null)
497         {
498             unusedBlackboxed~= varName;
499             return this;
500         }
501         sV._isBlackboxed = true;
502         return append(varName, sV);
503     }
504     /**
505     *   For speed sake, it doesn't check whether it is valid.
506     *   That means both a valid and invalid variable would return false (meaning used.)
507     */
508     bool isUnused(string varName) @nogc const
509     {
510         foreach(v; unusedBlackboxed) if(v == varName) return true;
511         return false;
512     }
513 
514     final size_t getLayoutSize(){return lastPosition;}
515     final void setAdditionalData(void* d, bool isAllocated)
516     {
517         this.additionalData = d;
518         this.isAdditionalAllocated = isAllocated;
519     }
520     final const(void*) getAdditionalData() const {return cast(const(void*))additionalData;}
521 
522     auto opDispatch(string member)()
523     {
524         return variables[member].sVar;
525     }
526 
527     void dispose()
528     {
529         import core.stdc.stdlib:free;
530         foreach (ref v; variables)
531         {
532             v.sVar.dispose();
533             v.alignment = 0;
534             v.size = 0;
535             v.sVar = null;
536         }
537         if(data != null)
538             free(data);
539         if(isAdditionalAllocated && additionalData != null)
540             free(additionalData);
541         additionalData = null;
542         data = null;
543     }
544 }
545 
546 
547 private bool isShaderVarNameValid(ref string varName)
548 {
549     import hip.util.string : indexOf;
550     
551     return varName.length > 0 && 
552     varName.indexOf(" ") == -1;
553 }